<?php

namespace System\Core\Errors;

use System\Lib\Log;
use System\Core\Controller;
use System\Core\Engineer;
use System\Core\Applications;
use System\Core\Config;
use \System\Types;

class Error extends Types\Object
{

    public $action;
    public $severity;
    public $message;
    public $filename;
    public $line;
    public $ob_level;
    public $levels = array(
        E_ERROR => 'Erro',
        E_WARNING => 'Atenção',
        E_PARSE => 'Erro de análise',
        E_NOTICE => 'Aviso',
        E_CORE_ERROR => 'Erro de núcleo',
        E_CORE_WARNING => 'Aviso de núcleo',
        E_COMPILE_ERROR => 'Erro de compilação',
        E_COMPILE_WARNING => 'Aviso de compilação',
        E_USER_ERROR => 'Erro de usuário',
        E_USER_WARNING => 'Alerta de usuário',
        E_USER_NOTICE => 'Aviso de usuário',
        E_STRICT => 'Aviso de tempo de execução'
    );

    /**
     * @var Error
     */
    private static $_instance;

    /**
     * @var \System\Core\Engineer
     */
    private static $_eng = null;
    private static $_fistLoad = true;

    public function __construct(Engineer $eng = null)
    {
        $this->ob_level = \ob_get_level();
        // Note:  Do not log messages from this constructor.

        if ($eng)
            self::$_eng = $eng;

        if (self::$_fistLoad)
        {
            \set_error_handler(array($this, '_error_handler'));

            \set_exception_handler(array($this, '_exception_handler'));

            self::$_fistLoad = false;
        }
    }

    /**
     * @return \System\Core\Errors\Error
     */
    public static function &GetInstance()
    {
        if (!self::$_instance)
            self::$_instance = new self();

        return self::$_instance;
    }

    public static function ShowErr($message, $status_code = 500, $heading = 'Um erro foi encontrado.')
    {
        $_error = self::GetInstance();
        $_error->ShowError($heading, $message, 'error_general', $status_code);
        die();
    }

    public static function Show404Error($page = '', $log_error = true)
    {
        $_error = self::GetInstance();
        $_error->Show404($page, $log_error);
        die();
    }

    // --------------------------------------------------------------------

    /**
     * Exception Logger
     *
     * This function logs PHP generated error messages
     *
     * @access	private
     * @param	string	the error severity
     * @param	string	the error string
     * @param	string	the error filepath
     * @param	string	the error line number
     * @return	string
     */
    private function logException($severity, $message, $filepath, $line, $context = null)
    {
        $severity = (!isset($this->levels[$severity])) ? $severity : $this->levels[$severity];

        $msg = "Severity: {$severity} --> {$message} {$filepath} {$line} {$context}";

        Log::Write($msg, Log::ERROR, true);
    }

    // --------------------------------------------------------------------

    /**
     * 404 Page Not Found Handler
     *
     * @param	string
     * @return	string
     */
    public function Show404($page = '', $log_error = true)
    {
        if (\method_exists(self::$_eng, 'disableMasterPage'))
            self::$_eng->disableMasterPage();
        else if (\method_exists(self::$_eng, 'Disable'))
            self::$_eng->Disable();

        $heading = "404 Página não encontrada";
        $message = "A página solicitada não foi encontrada.";

        // By default we log this, but allow a dev to skip it
        if ($log_error)
        {
            Log::Write("404 Página não encontrada --> {$page}");
        }

        echo $this->ShowError($heading, $message, 'error_404', 404);
        die();
    }

    // --------------------------------------------------------------------

    /**
     * General Error Page
     *
     * This function takes an error message as input
     * (either as a string or an array) and displays
     * it using the specified template.
     *
     * @param	string	the heading
     * @param	string	the message
     * @param	string	the template name
     * @return	string
     */
    public function ShowError($heading, $message, $template = 'error_general', $status_code = 500)
    {
        self::$_eng->SetStatusHeader($status_code);

        $message = '<p>' . \implode('</p><p>', ((!\is_array($message)) ? array($message) : $message)) . '</p>';

        if (\ob_get_level() > $this->ob_level + 1)
        {
            \ob_end_flush();
        }
        \ob_start();
        include(Applications::GetAppPath() . "Errors" . \DIRECTORY_SEPARATOR . $template . \EXT);
        $buffer = \ob_get_contents();
        \ob_end_clean();
        $heading = $heading;
        echo $buffer;
        die();
    }

    // --------------------------------------------------------------------

    /**
     * Native PHP error handler
     *
     * @param	string	the error severity
     * @param	string	the error string
     * @param	string	the error filepath
     * @param	string	the error line number
     * @return	string
     */
    public function ShowPhpError($severity, $message, $filepath, $line, $context = null)
    {
        $message = $message;
        $line = $line;
        $context = $context;
        $severity = (!isset($this->levels[$severity])) ? $severity : $this->levels[$severity];

        $filepath = \str_replace("\\", "/", $filepath);

        // For safety reasons we do not show the full file path
        if (false !== \strpos($filepath, '/'))
        {
            $x = \explode('/', $filepath);
            $filepath = $x[\count($x) - 2] . '/' . \end($x);
        }

        if (\ob_get_level() > $this->ob_level + 1 || Controller::getMode() === Controller::RENDERMODE)
        {
            \ob_end_flush();
        }

        \ob_start();
        include(Applications::GetAppErrorPath(Applications::PHPERROR));
        $buffer = \ob_get_contents();
        \ob_end_clean();
        echo $buffer;
    }

    /**
     * Exception Handler
     *
     * This is the custom exception handler that is declaired at the top
     * of Codeigniter.php.  The main reason we use this is to permit
     * PHP errors to be logged in our own log files since the user may
     * not have access to server logs. Since this function
     * effectively intercepts PHP errors, however, we also need
     * to display errors based on the current error_reporting level.
     * We do that with the use of a PHP error template.
     *
     * @access	private
     * @return	void
     */
    public function _error_handler($severity, $message, $filepath, $line, $context = null)
    {
        // We don't bother with "strict" notices since they tend to fill up
        // the log file with excess information that isn't normally very helpful.
        // For example, if you are running PHP 5 and you use version 4 style
        // class functions (without prefixes like "public", "private", etc.)
        // you'll get notices telling you that these have been deprecated.
        if ($severity == \E_STRICT)
        {
            return;
        }

        // Should we display the error? We'll get the current error_reporting
        // level and add its bits with the severity bits to find out.
        if (($severity & \error_reporting()) == $severity)
        {
            $this->ShowPhpError($severity, $message, $filepath, $line, $context);
        }

        // Should we log the error?  No?  We're done...
        if (Config::Get(Config::LOGS)->Get('log_threshold')->Valor == 0)
        {
            return;
        }

        $this->logException($severity, $message, $filepath, $line, $context);
    }

    /**
     * @access private
     */
    public function _exception_handler($ex)
    {
        $severity = \E_ERROR | \E_USER_ERROR;
        $message = $ex->getMessage();
        $filepath = $ex->getFile();
        $line = $ex->getLine();
        $context = $ex->getTraceAsString();

        $this->_error_handler($severity, $message, $filepath, $line, $context);
    }

}
